package com.tiantian.subject.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.tiantian.common.core.constant.CacheConstants;
import com.tiantian.common.core.constant.SystemConfigConstants;
import com.tiantian.common.core.constant.TopicConstants;
import com.tiantian.common.core.domain.LoginUser;
import com.tiantian.common.core.domain.dto.AnswerTimeDAO;
import com.tiantian.common.core.enums.UserType;
import com.tiantian.common.core.exception.BusinessException;
import com.tiantian.common.core.utils.DateUtils;
import com.tiantian.common.core.utils.StreamUtils;
import com.tiantian.common.redis.utils.RedisUtils;
import com.tiantian.common.satoken.utils.FieldUtils;
import com.tiantian.common.satoken.utils.LoginHelper;
import com.tiantian.subject.domain.dto.RecoverSubjectDto;
import com.tiantian.subject.domain.entity.Subject;
import com.tiantian.subject.domain.entity.TopicTag;
import com.tiantian.subject.domain.vo.SubjectOrderVo;
import com.tiantian.subject.domain.vo.SubjectVo;
import com.tiantian.subject.mapper.SubjectMapper;
import com.tiantian.subject.mapper.TopicTagMapper;
import com.tiantian.subject.service.SubjectService;
import com.tiantian.subject.service.UserSolveService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.tiantian.common.core.utils.PageUtils.startPage;

/**
 * @author tiantian
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class SubjectServiceImpl implements SubjectService {

    private final SubjectMapper subjectMapper;
    private final UserSolveService userSolveService;
    private final TopicTagMapper topicTagMapper;
    private final LockTemplate lockTemplate;
    private static final String LOCK_GLOBAL_SUBJECT_KEY = "lock:global:subject";

    @Override
    public Subject getSubjectDetail(String topicId) {
        UserType userType = LoginHelper.getUserType();
        if (userType == UserType.SYS_USER) {
            // 工作人员则直接返回
            Subject subject = subjectMapper.selectById(topicId);
            setSubjectTagList(topicId, subject);
            return subject;
        } else {
            // 判断是否在答题时间范围内
            AnswerTimeDAO ansTime = RedisUtils.getCacheObject(SystemConfigConstants.SYSTEM_CONFIG_ANSWER_TIME_KEY);
            LocalDateTime localDateTimeNow = DateUtils.getCurrentDateTime();
            LocalDateTime startTime = LocalDateTimeUtil.parse(ansTime.getDate() + " " + ansTime.getStart(), DateUtils.YYYY_MM_DD_HH_MM_SS);
            LocalDateTime endTime = LocalDateTimeUtil.parse(ansTime.getDate() + " " + ansTime.getEnd(), DateUtils.YYYY_MM_DD_HH_MM_SS);
            if (!(localDateTimeNow.isAfter(startTime) && localDateTimeNow.isBefore(endTime))) {
                throw new BusinessException("当前时间不在答题时间范围内, 查询题目失败");
            } else {
                // 设置题目标签
                Subject subject = subjectMapper.selectById(topicId);
                setSubjectTagList(topicId, subject);
                return subject;
            }
        }
    }

    @Override
    public List<SubjectVo> selectTopicVOList(Subject subject) {
        List<SubjectVo> subjectVos = subjectMapper.selectTopicVOList(subject);
        return getSubjectVoList(subjectVos);
    }

    @Override
    public Map<String, Object> setTopic(Subject subject) {
        if (checkAnsweringPeriod()) {
            throw new BusinessException("在答题时间内不允许设置题目");
        }
        List<String> tags = subject.getTags();
        checkTags(tags);
        LoginUser loginUser = LoginHelper.getLoginUser();
        // 雪花算法生成全局唯一id
        String topicId = IdUtil.getSnowflake().nextIdStr();
        subject.setTopicId(topicId);
        subject.setAuthorId(loginUser.getUserId());
        subject.setStatus(TopicConstants.EXAMINE_PASS);
        subject.setAuthorName(loginUser.getNickName());
        // 填充公共字段
        FieldUtils.insertAndSetCommonFields(subject);
        // 获取锁
        final LockInfo lock = lockTemplate.lock(LOCK_GLOBAL_SUBJECT_KEY);
        if (lock == null) {
            // 在等待时间内未能获取到锁
            throw new BusinessException("业务繁忙,请稍后重试...");
        }
        Boolean operationResult;
        try {
            // 判断序号是否符合范围和冲突
            checkOrder(subject.getOrder());
            operationResult = subjectMapper.insertBatch(subject);
        } catch (Exception e) {
            log.error("新增题目出现异常, message: {}, cause: {}", e.getMessage(), e.getCause());
            throw new BusinessException(StrUtil.format("新增题目异常,请稍后重试并联系管理员修复, {}", e));
        } finally {
            // 释放锁
            lockTemplate.releaseLock(lock);
        }
        // 保存题目标签
        List<TopicTag> tagList = new ArrayList<>();
        if (operationResult) {
            tagList = tags.stream().map(tag -> new TopicTag(null, topicId, tag)).collect(Collectors.toList());
        }
        topicTagMapper.insertBatch(tagList);
        // 构造响应
        return buildResponse(subject, operationResult);
    }

    private void checkTags(List<String> tags) {
        if (CollUtil.isEmpty(tags)) {
            throw new BusinessException("题目标签不能为空");
        }
        // 过滤tags为空的值
        tags.removeIf(StrUtil::isEmpty);
    }

    private void checkOrder(Integer order) {
        if (order < 1 || order > 11) {
            throw new BusinessException("序号范围只能从1-10");
        }
        // 判断序号是否冲突
        if (subjectMapper.checkOrderUnique(order) > 0) {
            throw new BusinessException("序号已存在请更换");
        }
    }

    @Override
    public Boolean updateById(Subject subject) {
        if (checkAnsweringPeriod()) {
            throw new BusinessException("在答题时间内不允许修改题目");
        }
        List<String> tags = subject.getTags();
        checkTags(tags);
        final LockInfo lock = lockTemplate.lock(LOCK_GLOBAL_SUBJECT_KEY);
        if (lock == null) {
            // 在等待时间内未能获取到锁
            throw new BusinessException("业务繁忙,请稍后重试...");
        }
        Boolean operationResult;
        try {
            // 判断序号是否符合范围和冲突
            checkOrder(subject.getOrder());
            operationResult = subjectMapper.updateById(subject);
        } catch (Exception e) {
            log.error("修改题目出现异常, message: {}, cause: {}", e.getMessage(), e.getCause());
            throw new BusinessException(StrUtil.format("新增题目异常,请稍后重试并联系管理员修复, {}", e));
        } finally {
            // 释放锁
            lockTemplate.releaseLock(lock);
        }
        String topicId = subject.getTopicId();
        // 过滤tags为空的值
        tags.removeIf(ObjUtil::isEmpty);
        // 删除题目标签
        topicTagMapper.deleteByTopicId(topicId);
        // 保存题目标签
        List<TopicTag> tagList = tags.stream().map(tag -> new TopicTag(null, topicId, tag)).collect(Collectors.toList());
        topicTagMapper.insertBatch(tagList);
        FieldUtils.updateCommonFields(subject);
        // 删除缓存
        deleteSubjectListCache();
        return operationResult;
    }

    @Override
    public Boolean logicalDeleteById(String topicId, Long userId) {
        // 删除缓存
        if (checkAnsweringPeriod()) {
            throw new BusinessException("在答题时间内不允许删除题目");
        }
        deleteSubjectListCache();
        return subjectMapper.logicalDeleteById(topicId, userId);
    }

    @Override
    public Boolean deleteById(String topicId, Long userId) {
        if (checkAnsweringPeriod()) {
            throw new BusinessException("在答题时间内不允许删除题目");
        }
        return subjectMapper.deleteById(topicId, userId);
    }

    @Override
    public int getSubjectTotal() {
        return subjectMapper.selectSubjectTotal();
    }

    @Override
    public List<SubjectVo> getSubjectList(Subject subject) {
        // 获取登录用户判断类型
        LoginUser loginUser = LoginHelper.getLoginUser();
        Long userId = loginUser.getUserId();
        // 协会内用户直接返回题目数据
        if (loginUser.getUserType().equals(UserType.SYS_USER.getUserType())) {
            return getSubjectVos(subject, userId);
        }
        // 若不在答题时间内返回空列表
        if (!checkAnsweringPeriod()) {
            return Collections.emptyList();
        }
        return getSubjectVos(subject, userId);
    }

    @Override
    public List<SubjectVo> selectRecycleBin(Subject subject) {
        List<SubjectVo> subjectVos = subjectMapper.selectRecycleBin(subject);
        return getSubjectVoList(subjectVos);
    }

    private List<SubjectVo> getSubjectVoList(List<SubjectVo> subjectVos) {
        if (CollUtil.isEmpty(subjectVos)) {
            return new ArrayList<>(0);
        }
        for (SubjectVo subjectVo : subjectVos) {
            String topicId = subjectVo.getTopicId();
            // 设置题目标签
            setSubjectTagList(subjectVo, topicId);
        }
        // 根据序号进行排序
        return StreamUtils.sorted(subjectVos, Comparator.comparingInt(SubjectVo::getOrder));
    }

    @Override
    public List<SubjectOrderVo> getSubjectOrderList() {
        List<SubjectOrderVo> subjectOrderVoList = new ArrayList<>();
        Set<Integer> usedOrders = subjectMapper.getUsedOrders();
        for (int order = 1; order <= 10; order++) {
            boolean disabled = usedOrders.contains(order);
            SubjectOrderVo subjectOrderVo = SubjectOrderVo.builder().order(order).disabled(disabled).build();
            subjectOrderVoList.add(subjectOrderVo);
        }
        return subjectOrderVoList;
    }

    @Override
    public int recoverSubject(RecoverSubjectDto recoverSubjectDto) {
        String topicId = recoverSubjectDto.getTopicId();
        Integer order = recoverSubjectDto.getOrder();
        if (!checkSubjectExist(topicId)) {
            throw new BusinessException("题目编号不存在");
        }
        Long userId = LoginHelper.getUserId();
        int operRows;
        // 加锁
        final LockInfo lock = lockTemplate.lock(LOCK_GLOBAL_SUBJECT_KEY);
        if (lock == null) {
            // 在等待时间内未能获取到锁
            throw new BusinessException("业务繁忙,请稍后重试...");
        }
        try {
            checkOrder(order);
            operRows = subjectMapper.recoverSubject(topicId, order, userId);
        } catch (Exception e) {
            log.error("恢复题目出现异常,message: {}, cause: {}", e.getMessage(), e.getCause());
            throw new BusinessException(StrUtil.format("新增题目异常,请稍后重试并联系管理员修复: {}", e));
        } finally {
            // 释放锁
            lockTemplate.releaseLock(lock);
        }
        return operRows;
    }


    @Override
    public boolean checkSubjectExist(String topicId) {
        return subjectMapper.checkSubjectExist(topicId) > 0;
    }

    private List<SubjectVo> getSubjectVos(Subject subject, Long userId) {
        List<SubjectVo> subjectVos = retrieveSubjectVosFromCacheOrDatabase(subject);
        updateSubjectVosStatusAndTags(subjectVos, userId);
        return StreamUtils.sorted(subjectVos, Comparator.comparingInt(SubjectVo::getOrder));
    }

    private List<SubjectVo> retrieveSubjectVosFromCacheOrDatabase(Subject subject) {
        startPage();
        String cacheKey = CacheConstants.SUBJECT_TABLE_INFO;
        List<SubjectVo> cachedSubjectVos = RedisUtils.getCacheObject(cacheKey);
        if (ObjUtil.isNotNull(cachedSubjectVos)) {
            return cachedSubjectVos;
        } else {
            cachedSubjectVos = subjectMapper.selectTopicVOList(subject);
            RedisUtils.setCacheObject(cacheKey, cachedSubjectVos);
        }
        return cachedSubjectVos;
    }

    private void deleteSubjectListCache() {
        RedisUtils.deleteObject(CacheConstants.SUBJECT_TABLE_INFO);
    }

    private void updateSubjectVosStatusAndTags(List<SubjectVo> subjectVos, Long userId) {
        for (SubjectVo subjectVo : subjectVos) {
            String topicId = subjectVo.getTopicId();
            int count = userSolveService.checkReplyUnique(topicId, userId);
            // 设置完成状态
            subjectVo.setIsComplete(count > 0 ? TopicConstants.FINISHED : TopicConstants.UNCOMPLETED);
            // 设置题目标签
            setSubjectTagList(subjectVo, topicId);
        }
    }

    private void setSubjectTagList(SubjectVo subjectVO, String topicId) {
        List<String> tagList = topicTagMapper.selectByTopicId(topicId);
        subjectVO.setTags(CollUtil.isEmpty(tagList) ? new ArrayList<>(0) : tagList);
    }

    private void setSubjectTagList(String topicId, Subject subject) {
        if (ObjUtil.isNull(subject)) {
            return;
        }
        List<String> tagList = topicTagMapper.selectByTopicId(topicId);
        subject.setTags(CollUtil.isEmpty(tagList) ? new ArrayList<>() : tagList);
    }

    private Map<String, Object> buildResponse(Subject subject, boolean operationResult) {
        Map<String, Object> ajax = new HashMap<>();
        ajax.put("isSetting", operationResult);
        ajax.put("title", subject.getTitle());
        ajax.put("authorName", subject.getAuthorName());
        ajax.put("topicId", subject.getTopicId());
        ajax.put("createTime", subject.getCreateTime());
        if (operationResult) {
            // 删除缓存
            deleteSubjectListCache();
        }
        return ajax;
    }

    public boolean checkAnsweringPeriod() {
        // 获取系统设置答题的时间
        AnswerTimeDAO ansTime = RedisUtils.getCacheObject(SystemConfigConstants.SYSTEM_CONFIG_ANSWER_TIME_KEY);
        if (ObjectUtil.isEmpty(ansTime)) {
            return false;
        }
        LocalDateTime localDateTimeNow = DateUtils.getCurrentDateTime();
        LocalDateTime startTime = LocalDateTimeUtil.parse(ansTime.getDate() + " " + ansTime.getStart(), "yyyy-MM-dd HH:mm:ss");
        LocalDateTime endTime = LocalDateTimeUtil.parse(ansTime.getDate() + " " + ansTime.getEnd(), "yyyy-MM-dd HH:mm:ss");
        // 判断是否在答题时间范围内
        return localDateTimeNow.isAfter(startTime) && localDateTimeNow.isBefore(endTime);
    }
}


